import { Observable, Subscriber, Subscribable, from } from "rxjs";
import { WebSocketSubject } from "rxjs/webSocket";
import { filter as filterOperator } from "rxjs/operators";
import {
  LoginRes,
  MethondRes,
  AgentEventData,
  OtherEventData,
  MediaEventData
} from "./call.type";
import CallSocketService from "./call-socket.service";
import appConfig from "~/config/app.config";
import JSEncrypt from "jsencrypt";

export default class CallOperation {
  private socketSubject: WebSocketSubject<MethondRes<any>>;

  constructor() {
    this.socketSubject = CallSocketService.callCenterSocket;
    const check = this.socketSubject && !this.socketSubject.hasError;
    if (!check) {
      throw new Error("无效的socket链接");
    }
  }

  /**
   * 监听socket 发送坐席事件
   */
  public onAgentEvent(): Observable<AgentEventData> {
    return this.eventFilter<AgentEventData>("AgentEvent");
  }

  /**
   * 监听socket 发送其他事件
   */
  public onOtherEvent() {
    return this.eventFilter<OtherEventData>("OtherEvent");
  }

  /**
   * 监听socket 发送媒体事件
   */
  public onMediaEvent() {
    return this.eventFilter<MediaEventData>("MediaEvent");
  }

  /**
   * 设置坐席状态
   * @param busy
   */
  public setUserStatus(busy = true) {
    const reqData = {
      agentStatus: busy ? "3" : "2",
      restReason: busy ? "示忙" : "示闲"
    };
    this.sendMessage("SetAgentStatus", reqData);
  }




  /**
   * 坐席登录
   * @param agentNo 坐席分机
   */
  public login(agentNo: string): Observable<LoginRes> {
    return new Observable<any>(subscriber => {
      this.queryRsa().subscribe(
        x => {
          this.sendlogon(x.keyId, x.pubKey, agentNo);
        },
        error => subscriber.error(error)
      );

      // 拦截登录数据
      const LoginSubscription = this.socketSubject
        .pipe(filterOperator(x => x.methodName === "UserLogon"))
        .subscribe(x => {
          LoginSubscription.unsubscribe();
          if (x.rstCode === "16") {
            subscriber.next({});
          } else if (x.rstCode === "0") {
            subscriber.next(x.rstObj);
          } else {
            subscriber.error(x.rstMsg);
          }
          subscriber.complete();
        });
    });
  }

  // public forceLogin(): Observable<LoginRes> {
  //   return new Observable(subscriber => {
  //     this.queryRsa().subscribe(
  //       x => {
  //         this.sendlogon(x.keyId, x.pubKey);
  //       },
  //       error => subscriber.error(error)
  //     );
  //     // 拦截登录数据
  //     const LoginSubscription = this.socketSubject
  //       .pipe(filterOperator(x => x.methodName === "UserLogon"))
  //       .subscribe(x => {
  //         LoginSubscription.unsubscribe();
  //         if (x.rstCode === "0") {
  //           subscriber.next(x.rstObj);
  //         } else {
  //           subscriber.error(x.rstMsg);
  //         }
  //         subscriber.complete();
  //       });
  //   });
  // }

  /**
   * 外呼
   * @param phoneNum 外地号码要 +0
   * @example
   * const phoneNum = 018812341234
   * // return number 0018812341234
   */
  public callOut(phoneNum: string): Observable<AgentEventData | null> {
    // 因为是外线，所以拨号前+0
    const callNum = `0${phoneNum}`;
    return new Observable(subscriber => {
      this.sendMessage("OutboundCall", {
        dest: callNum,
        type: 1
      });
      const outBoundSubscription = this.socketSubject
        .pipe(filterOperator(x => x.methodName === "OutboundCall"))
        .subscribe(x => {
          if (x.rstCode !== "0") {
            subscriber.error(x.rstMsg);
          } else {
            subscriber.next(null);
            listenAgentStatus();
          }
          outBoundSubscription.unsubscribe();
        });

      const listenAgentStatus = () => {
        const callSuccessSubscription = this.onAgentEvent()
          .pipe(
            filterOperator(
              x => x.agentStatus === "8" && x.originalDnis === callNum
            )
          )
          .subscribe(x => {
            subscriber.next(x);
            subscriber.complete();
            callSuccessSubscription.unsubscribe();
          });
      };
    });
  }

  /**
   * 挂断电话
   */
  public hangUp() {
    return new Observable(observer => {
      this.sendMessage("ReleaseCall");
      const subscription = this.socketSubject
        .pipe(filterOperator(x => x.methodName === "ReleaseCall"))
        .subscribe(x => {
          if (x.rstCode === "0") {
            observer.next("OK");
          } else {
            observer.error(x.rstMsg);
          }
          observer.complete();
          subscription.unsubscribe();
        });
    });
  }

  /**
   * 发送消息
   * @param data
   */
  public sendMessage(methodName: string, data?: any) {
    const reqData = { methodName, ...(data || {}) };
    this.socketSubject.next(reqData as any);
  }


  private queryRsa(): Observable<RsaType> {
    return new Observable(subscribe => {
      this.sendMessage("QueryRsaKey");
      this.socketSubject
        .pipe(filterOperator(x => x.methodName === "QueryRsaKey"))
        .subscribe(data => {
          if (data.rstCode === "0") {
            subscribe.next(data.rstObj);
          } else {
            subscribe.error(data.rstMsg);
          }
          subscribe.complete();
        });
    });
  }

  private sendlogon(keyId: string, pubKey: string, agentNo?: string) {
    const data = {
      methodName: "UserLogon",
      entPhoneNo: appConfig.callCenter.entPhoneNo, //企业号
      comAgentName: agentNo, //工号/软电话
      comAgentPwd: "Ffcs_2019", //密码
      fcSignin: "0",
      skillId: appConfig.callCenter.skilld, // 技能组
      userSkillLevel: "1",
      comStaffNo: "", // 工号/软电话
      comAgentPhone: "",
      reqIdx: "4cf85aee-78c8-3587-36231205", // 通讯的序列号
      rsaKey: keyId
    };
    const encrypt = new JSEncrypt();
    encrypt.setPublicKey(pubKey);
    // 使用公钥对密码进行加密
    data.comAgentPwd = encrypt.encrypt("Ffcs_2019");
    this.sendMessage("UserLogon", data);
  }

  private eventFilter<T>(eventName: string): Observable<T> {
    return this.socketSubject.pipe(
      filterOperator((x: any) => x.eventName === eventName)
    );
  }
}

type RsaType = {
  /**
   * 过期时间字符串
   */
  expire: string;
  keyId: string;
  pubKey: string;
};
